#Copyright 2012 Daniel C.(llaniscudani@gmail.com)

#This file is part of Radius3000.

#Radius3000 is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.

#Radius3000 is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.

#You should have received a copy of the GNU General Public License
#along with Radius3000.  If not, see <http://www.gnu.org/licenses/>.

#radius.py is the RADIUS Library

__version__ = "0.1"

import logging
import socketserver
import hashlib
import hmac
import random



class RADIUSRequestHandler(socketserver.BaseRequestHandler):

    def _responseAuth(self, packet):
        # MD5(Code+ID+Length+RequestAuth+Attributes+Secret)
        logging.debug('[RADIUS] Generating Response-Authenticator...')
        return hashlib.md5(packet + self.shared_secret).digest()

        
    def _messageAuthenticator(self, packet, attr):
        # Message-Authenticator (Id=80 1byte, Len=18 1byte, msg 16bytes) = 
        # HMAC-MD5 (Type, Identifier, Length, Request Authenticator
        # , Attributes(incl message auth packet with 16 nulls)) pack len = 18bytes
        logging.debug("[RADIUS] Generating Message-Authenticator")
        attr += b'\x50' + b'\x12' + (b'\x00' * 16)
        return b'\x50' + b'\x12' + hmac.new(self.shared_secret, (packet+attr)).digest()


    def _eapMessage(self, p):
        # EAP-Message (radius attribute)
        # encapsulate eap packet in radius eap-message attribute
        # type 79 = EAP-Message. Len >= 3. String = EAP Packet
        
        #read eap packet (remove first 2 id, len bytes)
        eapMsg = b''

        while p:
            #eapmsg + len + msg
            #split packet if exc 253 bytes
            if len(p) > 253:
                #len 253 = eapmsg code(1byte) + len(1byte) + data = 255(0xFF)
                eapMsg += b'\x4f' + b'\xff' + p[:253]
                p = p[253:]
            else:
                eapMsg += b'\x4f' + (len(p) + 2).to_bytes(1, 'big') + p
                break
        
        logging.debug('[RADIUS] EAP-Message')
        return eapMsg
        

    def _accessChallenge(self, attr):
        # access challenge radius packet
        # params = attributes
        # Code=11(0x0b) for Access-Challenge.
        # Packet:
        # Code(1oct)+Identifier(1oct)+length(2oct)+
        # response-authenticator(16oct) + attributes(Xoct)
        ac = b'\x0b' + self.identifier.to_bytes(1, 'big')
        
        # packet Length
        # (code 1)+(id 1)+(len 2)+(resp-auth 18bytes) ALWAYS 20bytes + attributes
        ac += (38 + len(attr)).to_bytes(2, 'big')
        # generate message auth before response auth.
        attr += self._messageAuthenticator((ac + self.request_authenticator), attr)

        # response Authenticator (after calc message auth)
        ac += self._responseAuth((ac + self.request_authenticator + attr))
        ac += attr

        logging.debug('[RADIUS] Sending Access-Challenge...')
        return ac


    def _accessAccept(self, attr):
        # access accept radius packet
        # params = attributes
        # Code=2(0x02) for Access-Accept.
        # Packet:
        # Code(1oct)+Identifier(1oct)+length(2oct)+
        # response-authenticator(16oct) + attributes(Xoct)
        ac = b'\x02' + self.identifier.to_bytes(1, 'big')
        attr += b'\x01\x06dani'
        # packet Length
        # (code 1)+(id 1)+(len 2)+(resp-auth 18bytes) ALWAYS 20bytes + attributes
        ac += (38 + len(attr)).to_bytes(2, 'big')
        
        
        # generate message auth before response auth.
        attr += self._messageAuthenticator((ac + self.request_authenticator), attr)

        # response Authenticator (after calc message auth)
        ac += self._responseAuth((ac + self.request_authenticator + attr))
        ac += attr

        logging.debug('[RADIUS] Sending Access-Accept...')
        return ac


    def parse_request(self, data):
        #
        # parse
        # functions
        def _str(dat):
            # octects -> string
            return dat.decode("utf-8")
        
        
        def _int(dat):
            # octets -> int
            return int.from_bytes(dat, 'big')

        
        def _pwd_decrypt(self, p_hash):
            # decrypt radius User-Password
            logging.debug('[RADIUS] Decrypt User-Password...')
            # define pwd = bytes
            o = b''
            r = self.request_authenticator
            while p_hash:
                h = hashlib.md5(self.shared_secret + r).digest()
                for i in range(16):
                    o += (h[i] ^ p_hash[i]).to_bytes(1, 'big')
                # update cursor
                p_hash = p_hash[16:]
                last = p_hash[:16]
            # remove nulls from tail
            while o.endswith(b'\x00'):
                o = o[:-1]
            # conv to utf8
            o = o.decode('UTF_8')
            return o

        
        def _ipv4(dat):
            # IPv4 octects to string
            dat = '{0}.{1}.{2}.{3}'.format(int.from_bytes(dat[0:1], 'big'),
            int.from_bytes(dat[1:2], 'big'), int.from_bytes(dat[2:3], 'big'),
            int.from_bytes(dat[3:4], 'big'))
            return dat  


        def _bypass(dat):
            return dat


        #
        # parsing...
        #
        
        logging.debug("Parsing the packet...")

        # Code
        self.code = int.from_bytes(data[0:1], 'big')
        # Identifier
        self.identifier = int.from_bytes(data[1:2], 'big')
        # Length
        self.length = int.from_bytes(data[2:4], 'big')
        # Request Authenticator
        self.request_authenticator = data[4:20]
        
        
        if self.length != len(data):
            logging.error("[RADIUS] The request length missmatch")
            return
        
        # drop first 20 bytes
        data = data[20:]
        
        # RADIUS packet codes (some...)
        packetCodes = {
                1:'Access-Request',
                2:'Access-Accept',
                3:'Access-Reject',
                4:'Accounting-Request',
                5:'Accounting-Response',
                11:'Access-Challenge',
                }           
                    
        # RADIUS packet attribute codes (some...)
        attribute_codes = {
                1:('User-Name', _str),
                2:('User-Password', _pwd_decrypt),
                3:('CHAP-Password', _pwd_decrypt),
                4:('NAS-IP-Address', _ipv4),
                5:('NAS-Port', _int),
                12:('Framed-MTU', _int),
                24:('State', _str),
                30:('Called-Station-Id', _str),
                31:('Calling-Station-Id', _str),
                32:('NAS-Identifier', _str),
                61:('NAS-Port-Type', _str),
                77:('Connect-Info', _str),
                79:('EAP-Message', _bypass),
                80:('Message-Authenticator', _bypass),
                }
                

        logging.debug("[RADIUS] Parsing packet attributes...")
        # read packet attributes
        self.attributes = {}
        while data:
            # id del atributo
            at = attribute_codes[int.from_bytes(data[0:1], 'big')]
            # tamaño del atributo
            at_len = int.from_bytes(data[1:2], 'big')
            try:
                self.attributes[at[0]] = at[1](data[2:at_len])
            except KeyError:
                logging.error("[RADIUS] The packet code is not defined")
                return            
            # Drop x bytes of attribute and read next
            data = data[at_len:]
            
            
    def _ms_mppe_keys(self):
        # rfc2548 - 2.4.2 - p20

        def string(key_subfields):
            # salt code
            salt = (128 + random.getrandbits(7)).to_bytes(1, 'big')
            salt += random.getrandbits(8).to_bytes(1, 'big')
            
            #P = key_length + key sub-fields = MS-MPPE-Recv-Key + MS-MPPE-Send-Key
            P = b'\x80' + key_subfields
            
            #if len of P is 16 pass
            if len(P) != 16:
                #pad the string with nulls to multiple of 16
                P += (16 - (len(P) % 16)) * b'\x00'
                    
            ciphertext = b''
            #c0 is Request Authenticator + salt field
            c = self.request_authenticator + salt
            while P:
                b = hashlib.md5(self.shared_secret + c).digest()
                for i in range(16):
                    ciphertext += (P[i] ^ b[i]).to_bytes(1, 'big')
                #drop processed bytes
                P = P[16:]
                
            return salt + ciphertext

        
        #mppe recv
        mppe_recv = string(self.session_keys[0] + self.session_keys[1])
        #vendor specific string
        mppe_recv = b'\x11' + (len(mppe_recv) + 2).to_bytes(1, 'big') + mppe_recv
        #radius attribute + vendor id = microsoft 311(0x00 0x00 0x01 0x37)
        mppe_recv = b'\x1a' + (len(mppe_recv) + 6).to_bytes(1, 'big') + b'\x00\x00\x01\x37' + mppe_recv
                 
        #mppe send
        mppe_send = string(self.session_keys[0] + self.session_keys[1])
        #vendor specific string
        mppe_send = b'\x10' + (len(mppe_send) + 2).to_bytes(1, 'big') + mppe_send
        #radius attribute
        mppe_send = b'\x1a' + (len(mppe_send) + 6).to_bytes(1, 'big') + b'\x00\x00\x01\x37' + mppe_send
            
        return mppe_recv + mppe_send
                
        
    def access_request(self):
        #when receive a access request from user...
        """Override"""        
        

    def handle(self):
        #
        # handle socket
        # main function
        #
        #socket = self.request[1]
        
        #parse data
        self.parse_request(self.request[0])
        
        if self.code == 1:
            #if code = 1(access-request) do this
            self.access_request()
            

